/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// gl_vidnt.c -- NT GL vid component

#include "quakedef.h"

// our global d3d objects
ID3D10Device *d3d_Device = NULL;
IDXGISwapChain *d3d_SwapChain = NULL;
DXGI_MODE_DESC d3d_WindowedMode;

QTEXTURE d3d_RenderTarget;
QTEXTURE d3d_DepthBuffer;

// video cvars
extern cvar_t *vid_mode;
extern cvar_t *vid_fullscreen;
extern cvar_t *vid_width;
extern cvar_t *vid_height;
extern cvar_t *vid_vsync;


void VID_CreateDepthBuffer (QTEXTURE *DepthBuffer, int width, int height)
{
	// in case it had been previously allocated
	DepthBuffer->Release ();

	// create a texture for use as the depth buffer
	CD3D10_TEXTURE2D_DESC descDepth (
		DXGI_FORMAT_D24_UNORM_S8_UINT,
		width,
		height,
		1,
		1,
		D3D10_BIND_DEPTH_STENCIL,
		D3D10_USAGE_DEFAULT,
		0,
		1,
		0,
		0
	);

	if (FAILED (d3d_Device->CreateTexture2D (&descDepth, NULL, &DepthBuffer->Texture)))
		Sys_Error ("VID_CreateDepthBuffer : CreateTexture2D failed");

	if (FAILED (d3d_Device->CreateDepthStencilView (DepthBuffer->Texture, NULL, &DepthBuffer->DSV)))
		Sys_Error ("VID_CreateDepthBuffer : CreateDepthStencilView failed");

	// we don't need to hold a reference to the underlying texture object any more
	DepthBuffer->Texture->Release ();
	DepthBuffer->Texture = NULL;
}


DXGI_MODE_DESC *VID_VerifyMode (DXGI_MODE_DESC *mode, BOOL windowed)
{
	// ensure that it's an enumerated mode
	for (int i = 0; i < vid.NumModes; i++)
	{
		if (!memcmp (&vid.ModeList[i], mode, sizeof (DXGI_MODE_DESC)))
		{
			if (windowed)
			{
				// copy it over to the windowed mode desc
				memcpy (&d3d_WindowedMode, mode, sizeof (DXGI_MODE_DESC));

				// update for cvars
				if (vid_width->integer > 0) d3d_WindowedMode.Width = vid_width->integer;
				if (vid_height->integer > 0) d3d_WindowedMode.Height = vid_height->integer;

				// and return that
				return &d3d_WindowedMode;
			}
			else return mode;
		}
	}

	// if we didn't find an enumerated mode we force one
	if (windowed)
		return &d3d_WindowedMode;
	else return &vid.ModeList[vid.NumModes - 1];
}


void VID_UpdateModeParams (int width, int height)
{
	vid.Width = width;
	vid.Height = height;
	vid.RecalcRefdef = TRUE;
	vid.HandleResize = TRUE;
}


void D3D_CreateDevice (HWND hWnd, DXGI_MODE_DESC *mode, BOOL windowed)
{
	DXGI_SWAP_CHAIN_DESC sd;

	ZeroMemory (&sd, sizeof (sd));

	// fixup the mode
	mode = VID_VerifyMode (mode, windowed);

	// copy over the mode
	memcpy (&sd.BufferDesc, mode, sizeof (DXGI_MODE_DESC));

	// fill in the rest of the swap chain desc
	// we always create a windowed swapchain and rely on the first mode change to switch to fullscreen if desired
	sd.BufferCount = 1;
	sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
	sd.OutputWindow = hWnd;
	sd.SampleDesc.Count = 1;
	sd.SampleDesc.Quality = 0;
	sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	sd.Windowed = TRUE;

	HRESULT hr = D3D10CreateDeviceAndSwapChain (
		NULL,
		D3D10_DRIVER_TYPE_HARDWARE,
		NULL,
		D3D10_CREATE_DEVICE_SINGLETHREADED | D3D10_CREATE_DEVICE_ALLOW_NULL_FROM_MAP,
		D3D10_SDK_VERSION,
		&sd,
		&d3d_SwapChain,
		&d3d_Device
	);

	if (FAILED (hr))
	{
		Sys_Error ("D3D_CreateDevice : D3D10CreateDeviceAndSwapChain failed");
		return;
	}

	// now we disable stuff that we want to handle ourselves
	IDXGIFactory *pFactory = NULL;

	if (SUCCEEDED (d3d_SwapChain->GetParent (__uuidof (IDXGIFactory), (void **) &pFactory)))
	{
		pFactory->MakeWindowAssociation (hWnd, DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER);
		pFactory->Release ();
	}

	// set these up for the first time through, getting the correct params and forcing a mode change
	VID_UpdateModeParams (mode->Width, mode->Height);

	Con_Printf (PRINT_SAFE, "Initialized D3D10\n");
}


void D3D_VidChangeMode (DXGI_MODE_DESC *mode, BOOL windowed)
{
	if (!d3d_Device) return;
	if (!d3d_SwapChain) return;

	// retrieve the modified mode
	mode = VID_VerifyMode (mode, windowed);

	// see has the swapchain desc changed
	DXGI_SWAP_CHAIN_DESC newsd, oldsd;

	// get the desc twice for current and new
	d3d_SwapChain->GetDesc (&newsd);
	d3d_SwapChain->GetDesc (&oldsd);

	// update params in the new
	memcpy (&newsd.BufferDesc, mode, sizeof (DXGI_MODE_DESC));
	newsd.Windowed = windowed;

	// and now we can compare them
	if (!memcmp (&newsd, &oldsd, sizeof (DXGI_MODE_DESC)) && vid.Initialized)
	{
		Con_Printf (PRINT_DEVELOPER, "Present parameters are unchanged - mode change aborted\n");
		return;
	}

	// don't loop sound if this takes some time
	S_ClearBuffer ();

	// check for a transition and set the resize appropriately
	if (newsd.Windowed && !oldsd.Windowed)
	{
		d3d_SwapChain->SetFullscreenState (FALSE, NULL);
		d3d_SwapChain->ResizeTarget (&newsd.BufferDesc);
	}
	else if (!newsd.Windowed && oldsd.Windowed)
	{
		d3d_SwapChain->ResizeTarget (&newsd.BufferDesc);
		d3d_SwapChain->SetFullscreenState (TRUE, NULL);

		newsd.BufferDesc.RefreshRate.Numerator = 0;
		newsd.BufferDesc.RefreshRate.Denominator = 0;

		d3d_SwapChain->ResizeTarget (&newsd.BufferDesc);
	}
	else d3d_SwapChain->ResizeTarget (&newsd.BufferDesc);

	// no need to do anything else here; we just wait for a WM_SIZE and handle it
	if (windowed)
	{
		VID_CenterWindow (vid.Window);
		vid.LastWindowedMode = vid_mode->integer;
	}
	else vid.LastFullscreenMode = vid_mode->integer;
}


void VID_HandleWMSize (int width, int height)
{
	// WM_SIZE can be triggered before the device is created
	if (!vid.Initialized) return;
	if (!d3d_Device) return;
	if (!d3d_SwapChain) return;

	Con_Printf (PRINT_DEVELOPER, "\n\n\nGot a WM_SIZE to %i x %i\n", width, height);

	// this just signals that we need a mode change will will be triggered at the next frame
	VID_UpdateModeParams (width, height);

	// and skip frames so long as we keep getting WM_SIZE messages
	vid.SkipThisFrame = TRUE;
}


void VID_HandleResize (void)
{
	// clear all state and release the framebuffer
	d3d_Device->ClearState ();
	d3d_Device->OMSetRenderTargets (0, NULL, NULL);
	d3d_DepthBuffer.Release ();
	d3d_RenderTarget.Release ();

	// resize the buffers
	d3d_SwapChain->ResizeBuffers (0, vid.Width, vid.Height, DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH);

	// and recreate them
	if (SUCCEEDED (d3d_SwapChain->GetBuffer (0, __uuidof (ID3D10Texture2D), (LPVOID *) &d3d_RenderTarget.Texture)))
	{
		D3D10_TEXTURE2D_DESC desc;

		d3d_RenderTarget.Texture->GetDesc (&desc);

		if (SUCCEEDED (d3d_Device->CreateRenderTargetView (d3d_RenderTarget.Texture, NULL, &d3d_RenderTarget.RTV)))
		{
			SAFE_RELEASE (d3d_RenderTarget.Texture);

			// create our depth buffer texture and view here
			VID_CreateDepthBuffer (&d3d_DepthBuffer, vid.Width, vid.Height);

			// reset our render targets
			d3d_Device->OMSetRenderTargets (1, &d3d_RenderTarget.RTV, d3d_DepthBuffer.DSV);

			// recreate anything that needs to be recreated after a mode change
			VID_RunHandlers (handler_modechange);
			return;
		}

		Sys_Error ("Vid_CreateFrameBuffer : CreateRenderTargetView failed");
	}

	Sys_Error ("Vid_CreateFrameBuffer : Failed to get back buffer");
}


int VID_ModeSortFunc (DXGI_MODE_DESC *a, DXGI_MODE_DESC *b)
{
	if (a->Width == b->Width)
	{
		if (a->Height == b->Height)
		{
			if (a->RefreshRate.Numerator == b->RefreshRate.Numerator)
				return a->RefreshRate.Denominator - b->RefreshRate.Denominator;
			else return a->RefreshRate.Numerator - b->RefreshRate.Numerator;
		}
		else return a->Height - b->Height;
	}
	else return a->Width - b->Width;
}


/*
=================
VID_EnumerateModes
=================
*/
int VIDD3D_TryEnumerateModes (IDXGIOutput *output, DXGI_MODE_DESC *ModeList, DXGI_FORMAT fmt)
{
	UINT NumModes = 0;
	const UINT EnumFlags = (DXGI_ENUM_MODES_SCALING | DXGI_ENUM_MODES_INTERLACED);

	// get modes on this adapter (note this is called twice per design) - note that the first time ModeList must be NULL or it will return 0 modes
	if (SUCCEEDED (output->GetDisplayModeList (fmt, EnumFlags, &NumModes, NULL)))
	{
		if (SUCCEEDED (output->GetDisplayModeList (fmt, EnumFlags, &NumModes, ModeList)))
			return NumModes;
		else return 0;
	}
	else return 0;
}


void VID_EnumerateModes (void)
{
	IDXGIFactory *pFactory = NULL;
	IDXGIOutput *d3d11_Output = NULL;
	IDXGIAdapter *d3d11_Adapter = NULL;
	UINT NumModes = 0;
	DXGI_MODE_DESC *ModeList = NULL;
	QSCRATCHLOCKER Lock ((void **) &ModeList, __FUNCTION__);

	// fixme - enum these properly
	if (FAILED (CreateDXGIFactory (__uuidof (IDXGIFactory), (void **) &pFactory)))
		Sys_Error ("VIDD3D_EnumerateModes : CreateDXGIFactory1 failed");

	if ((pFactory->EnumAdapters (0, &d3d11_Adapter)) == DXGI_ERROR_NOT_FOUND)
		Sys_Error ("VIDD3D_EnumerateModes : IDXGIFactory failed to enumerate Adapter 0");

	if ((d3d11_Adapter->EnumOutputs (0, &d3d11_Output)) == DXGI_ERROR_NOT_FOUND)
		Sys_Error ("VIDD3D_EnumerateModes : IDXGIFactory failed to enumerate Outputs on Adapter 0");

	if ((NumModes = VIDD3D_TryEnumerateModes (d3d11_Output, ModeList, DXGI_FORMAT_R8G8B8A8_UNORM)) == 0)
		Sys_Error ("VIDD3D_EnumerateModes : Failed to enumerate any 32bpp modes!");

	d3d11_Output->Release ();
	d3d11_Adapter->Release ();
	pFactory->Release ();

	// create a buffer big enough for them all
	vid.ModeList = (DXGI_MODE_DESC *) zoneHeap->Alloc (NumModes * sizeof (DXGI_MODE_DESC));
	vid.NumModes = 0;

	// copy over the ones we want
	for (int i = 0; i < NumModes; i++)
	{
		if (ModeList[i].Width < VID_MINIMUM_W) continue;
		if (ModeList[i].Height < VID_MINIMUM_H) continue;
		if (ModeList[i].Width < ModeList[i].Height) continue;

		memcpy (&vid.ModeList[vid.NumModes], &ModeList[i], sizeof (DXGI_MODE_DESC));
		vid.NumModes++;
	}

	// and sort them by size
	qsort (vid.ModeList, vid.NumModes, sizeof (DXGI_MODE_DESC), (sortfunc_t) VID_ModeSortFunc);

	// store the last valid modes for both windowed and fullscreen so that we can Alt-Enter between them
	vid.LastWindowedMode = 0;
	vid.LastFullscreenMode = vid.NumModes - 1;

	// copy over the first mode to the windowed mode desc
	memcpy (&d3d_WindowedMode, &vid.ModeList[0], sizeof (DXGI_MODE_DESC));
}


/*
=================
GL_BeginRendering

=================
*/
BOOL GL_BeginRendering (void)
{
	if (!d3d_Device) return FALSE;
	if (!d3d_SwapChain) return FALSE;

	if (vid.HandleResize)
	{
		VID_HandleResize ();
		vid.HandleResize = FALSE;
		return FALSE;
	}

	if (vid.Occluded)
	{
		if (d3d_SwapChain->Present (0, DXGI_PRESENT_TEST) == S_OK)
			vid.Occluded = FALSE;
		else
		{
			Sleep (5);
			return FALSE;
		}
	}

	// see if a video mode change was queued for this frame
	if (vid.QueueModeChange)
	{
		// issue the mode switch (this may not happen if the mode is in reality unchanged)
		D3D_VidChangeMode (&vid.ModeList[vid_mode->integer], !vid_fullscreen->boolean);

		// and complete it by switching off the flag
		vid.QueueModeChange = FALSE;

		// skip this frame
		return FALSE;
	}

	if (vid.SkipThisFrame)
	{
		vid.SkipThisFrame = FALSE;
		return FALSE;
	}

	// anything else needed before the scene begins can go here

	// checks for changed user states and updates their objects as required
	VID_RunHandlers (hander_perframe);

	// began rendering OK
	return TRUE;
}


void GL_EndRendering (void)
{
	// don't swap buffers on a mapshot as we want to copy off the back buffer directly
	if (!scr.DrawMapshot)
	{
		HRESULT hr = S_OK;

		// just does a present now; timedemos should never vsync
		if (cls.timedemo)
			hr = d3d_SwapChain->Present (0, 0);
		else hr = d3d_SwapChain->Present (vid_vsync->integer, 0);

		if (hr == DXGI_STATUS_OCCLUDED)
			vid.Occluded = TRUE;
	}
}


void VID_ShutdownD3D (void)
{
	if (d3d_Device && d3d_SwapChain)
	{
		// clear to black
		d3d_Device->ClearRenderTargetView (d3d_RenderTarget.RTV, float4 (0, 0, 0, 0));
		d3d_SwapChain->Present (0, 0);

		// ensure that the window paints
		Sys_SendKeyEvents (TRUE);

		// free the shader cache; this only happens during shutdown
		R_FreeShaderCache ();

		// unbind all rendertargets and state
		d3d_Device->OMSetRenderTargets (0, NULL, NULL);
		d3d_Device->ClearState ();
		D3DX10UnsetAllDeviceObjects (d3d_Device);
	}

	// now release all our stuff
	VID_OnReleaseDevice ();

	d3d_RenderTarget.Release ();
	d3d_DepthBuffer.Release ();

	SAFE_RELEASE (d3d_SwapChain);
	SAFE_RELEASE (d3d_Device);
}

